Skip to content

Serializer: Fixes unsafe stream cast in FromStream<T>#5651

Merged
kirankumarkolli merged 7 commits intomasterfrom
users/nalutripician/fix-fromstream-cast-5620
Mar 11, 2026
Merged

Serializer: Fixes unsafe stream cast in FromStream<T>#5651
kirankumarkolli merged 7 commits intomasterfrom
users/nalutripician/fix-fromstream-cast-5620

Conversation

@NaluTripician
Copy link
Copy Markdown
Contributor

Description

Fixes #5620

Replaces the unsafe (T)(object)stream cast pattern with safe is pattern matching in all FromStream<T> serializer implementations across the SDK.

Problem

The FromStream<T> method in multiple serializer implementations uses the following pattern:

if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
    return (T)(object)stream;
}

typeof(Stream).IsAssignableFrom(typeof(T)) returns true when T is Stream or any subclass (e.g., MemoryStream, FileStream). If T is a specific Stream subclass but the runtime stream parameter is a different Stream type, the cast (T)(object)stream throws a raw InvalidCastException with no context about what went wrong.

Example that throws:

// T = MemoryStream, but stream is actually a FileStream at runtime
serializer.FromStream<MemoryStream>(someFileStream); // InvalidCastException!

Fix

Replaced the unsafe cast with safe is pattern matching:

if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
    if (stream is T typedStream)
    {
        return typedStream;
    }

    throw new InvalidCastException(
        $"Stream of type '{stream.GetType().FullName}' is not compatible "
        + $"with the requested type '{typeof(T).FullName}'.");
}

This provides:

  • ✅ Safe runtime type checking (no unexpected InvalidCastException)
  • ✅ A descriptive error message identifying both the actual and expected types
  • ✅ No behavioral change for the common case (T = Stream)

Files Changed

Core SDK Serializers (2):

  • Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs
  • Microsoft.Azure.Cosmos/src/Serializer/CosmosSystemTextJsonSerializer.cs

Sample Code (3):

  • Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs
  • Microsoft.Azure.Cosmos.Samples/Usage/ReEncryption/ReEncryptionSupport/ReEncryptionJsonSerializer.cs
  • Microsoft.Azure.Cosmos.Samples/Usage/ItemManagement/Program.cs

Encryption Modules (2):

  • Microsoft.Azure.Cosmos.Encryption/src/CosmosJsonDotNetSerializer.cs
  • Microsoft.Azure.Cosmos.Encryption.Custom/src/Common/CosmosJsonDotNetSerializer.cs

Unit Tests (2):

  • Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosJsonSerializerUnitTests.cs — 2 new tests
  • Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Json/CosmosSystemTextJsonSerializerTest.cs — 2 new tests

Not Changed

  • PatchOperationCore{T}.cs — Has a similar-looking pattern but casts FROM T TO Stream (upcast), which always succeeds. No fix needed.
  • Test utility serializers — Internal test code, not customer-facing.

Testing

  • 4 new unit tests added (2 per serializer):
    • ValidateFromStreamWithBaseStreamType / TestFromStreamWithBaseStreamType — Confirms FromStream<Stream>(memoryStream) succeeds (regression test)
    • ValidateFromStreamWithIncompatibleStreamTypeThrowsDescriptiveError / TestFromStreamWithIncompatibleStreamTypeThrowsDescriptiveError — Confirms FromStream<FileStream>(memoryStream) throws InvalidCastException with a descriptive message containing both type names
  • All existing tests continue to pass

Replaces the unsafe (T)(object)stream cast pattern with safe 'is' pattern
matching in all FromStream<T> implementations. The old pattern could throw
an InvalidCastException when T was a Stream subclass (e.g., MemoryStream)
but the runtime stream was a different Stream type.

The fix uses 'stream is T typedStream' for safe runtime type checking and
throws a descriptive InvalidCastException when the types are incompatible.

Fixed in 7 files (2 core SDK serializers, 3 samples, 2 encryption modules).
Added unit tests for both CosmosJsonDotNetSerializer and
CosmosSystemTextJsonSerializer confirming the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NaluTripician NaluTripician changed the title [Internal] Serializer: Fixes unsafe stream cast in FromStream<T> Serializer: Fixes unsafe stream cast in FromStream<T> Mar 3, 2026
@NaluTripician NaluTripician self-assigned this Mar 3, 2026
Comment thread Microsoft.Azure.Cosmos.Encryption.Custom/src/Common/CosmosJsonDotNetSerializer.cs Outdated
Comment thread Microsoft.Azure.Cosmos.Encryption.Custom/src/Common/CosmosJsonDotNetSerializer.cs Outdated
NaluTripician and others added 2 commits March 4, 2026 11:39
Replaces the 3-part block (typeof guard + is check + throw) with
a simple 'if (stream is T typedStream)' pattern match across all
serializer files. Removes incompatible stream type tests since
mismatched cases now fall through to deserialization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

@NaluTripician NaluTripician left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed both comments — simplified to just if (stream is T typedStream) { return typedStream; } across all files.

Re: 'Is above if still needed?' — Good call, the outer typeof(Stream).IsAssignableFrom(typeof(T)) guard is no longer needed. The stream is T pattern match already handles the type check at runtime.

Re: 'Isn't it supposed to fallback to the copy?' — You're right. Removed the throw and let incompatible cases fall through to the normal deserialization path. The simplified stream is T check handles both concerns cleanly.

kirankumarkolli
kirankumarkolli previously approved these changes Mar 5, 2026
…ypes

The simplified 'stream is T' check was too broad - when T=object (e.g. dynamic),
it always matched, returning the raw stream instead of deserializing. Restored
the typeof(Stream).IsAssignableFrom(typeof(T)) guard in a combined condition.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kirankumarkolli kirankumarkolli merged commit 326532a into master Mar 11, 2026
32 checks passed
@kirankumarkolli kirankumarkolli deleted the users/nalutripician/fix-fromstream-cast-5620 branch March 11, 2026 23:29
Copilot AI mentioned this pull request Mar 18, 2026
4 tasks
NaluTripician added a commit that referenced this pull request Mar 24, 2026
## Description

Fixes #5620

Replaces the unsafe `(T)(object)stream` cast pattern with safe `is`
pattern matching in all `FromStream<T>` serializer implementations
across the SDK.

### Problem

The `FromStream<T>` method in multiple serializer implementations uses
the following pattern:

```csharp
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
    return (T)(object)stream;
}
```

`typeof(Stream).IsAssignableFrom(typeof(T))` returns `true` when `T` is
`Stream` **or any subclass** (e.g., `MemoryStream`, `FileStream`). If
`T` is a specific `Stream` subclass but the runtime `stream` parameter
is a different `Stream` type, the cast `(T)(object)stream` throws a raw
`InvalidCastException` with no context about what went wrong.

**Example that throws:**
```csharp
// T = MemoryStream, but stream is actually a FileStream at runtime
serializer.FromStream<MemoryStream>(someFileStream); // InvalidCastException!
```

### Fix

Replaced the unsafe cast with safe `is` pattern matching:

```csharp
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
    if (stream is T typedStream)
    {
        return typedStream;
    }

    throw new InvalidCastException(
        $"Stream of type '{stream.GetType().FullName}' is not compatible "
        + $"with the requested type '{typeof(T).FullName}'.");
}
```

This provides:
- ✅ Safe runtime type checking (no unexpected `InvalidCastException`)
- ✅ A descriptive error message identifying both the actual and expected
types
- ✅ No behavioral change for the common case (`T = Stream`)

### Files Changed

**Core SDK Serializers (2):**
- `Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs`
-
`Microsoft.Azure.Cosmos/src/Serializer/CosmosSystemTextJsonSerializer.cs`

**Sample Code (3):**
-
`Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs`
-
`Microsoft.Azure.Cosmos.Samples/Usage/ReEncryption/ReEncryptionSupport/ReEncryptionJsonSerializer.cs`
- `Microsoft.Azure.Cosmos.Samples/Usage/ItemManagement/Program.cs`

**Encryption Modules (2):**
- `Microsoft.Azure.Cosmos.Encryption/src/CosmosJsonDotNetSerializer.cs`
-
`Microsoft.Azure.Cosmos.Encryption.Custom/src/Common/CosmosJsonDotNetSerializer.cs`

**Unit Tests (2):**
-
`Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosJsonSerializerUnitTests.cs`
— 2 new tests
-
`Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Json/CosmosSystemTextJsonSerializerTest.cs`
— 2 new tests

### Not Changed

- **`PatchOperationCore{T}.cs`** — Has a similar-looking pattern but
casts FROM `T` TO `Stream` (upcast), which always succeeds. No fix
needed.
- **Test utility serializers** — Internal test code, not
customer-facing.

### Testing

- 4 new unit tests added (2 per serializer):
- `ValidateFromStreamWithBaseStreamType` /
`TestFromStreamWithBaseStreamType` — Confirms
`FromStream<Stream>(memoryStream)` succeeds (regression test)
- `ValidateFromStreamWithIncompatibleStreamTypeThrowsDescriptiveError` /
`TestFromStreamWithIncompatibleStreamTypeThrowsDescriptiveError` —
Confirms `FromStream<FileStream>(memoryStream)` throws
`InvalidCastException` with a descriptive message containing both type
names
- All existing tests continue to pass

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cast may not always works in certain edge cases

2 participants